package display.client;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

import track.Classification_enum;
import track.ObjectLocation;
import track.PointTrack;
import track.Pos;
import track.TrackState;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.dom.client.Document;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.maps.client.InfoWindowContent;
import com.google.gwt.maps.client.MapPane;
import com.google.gwt.maps.client.MapPaneType;
import com.google.gwt.maps.client.MapWidget;
import com.google.gwt.maps.client.control.LargeMapControl;
import com.google.gwt.maps.client.control.MenuMapTypeControl;
import com.google.gwt.maps.client.event.GroundOverlayVisibilityChangedHandler;
import com.google.gwt.maps.client.geom.LatLng;
import com.google.gwt.maps.client.geom.LatLngBounds;
import com.google.gwt.maps.client.geom.Point;
import com.google.gwt.maps.client.geom.Size;
import com.google.gwt.maps.client.overlay.GroundOverlay;
import com.google.gwt.maps.client.overlay.Icon;
import com.google.gwt.maps.client.overlay.Marker;
import com.google.gwt.maps.client.overlay.MarkerOptions;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.VerticalPanel;

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */
public class DisplayWebApp implements EntryPoint {
    private static int REFRESH_INTERVAL = 500; // ms

    private static boolean MAPS_DISPLAY = false;

    // Image url for the sensor circle
    private static final String circleImage = new String ("circle.png");

    // Image url for a track
    private static final String trackImage = new String ("track.png");

    // Image url for a track
    private static final String unknownTrackImage = new String ("track-blue.png");

    // Image url for a track
    private static final String friendlyTrackImage = new String ("track-green.png");

    // Image url for a track
    private static final String hostileTrackImage = new String ("track-red.png");

    // Indicates that an object has had classification information but no
    // position information.
    private Pos NO_POSITION = new Pos(-27, -27, -27);

    private VerticalPanel mainPanel = new VerticalPanel();
    private HorizontalPanel upperPanel = new HorizontalPanel ();
    private VerticalPanel infoAndSummaryPanel = new VerticalPanel ();
    private ScrollPanel infoTableScrollPanel = new ScrollPanel ();
    private FlexTable infoTable = new FlexTable ();
    private Grid summaryGrid = new Grid (2, 6);
    private AbsolutePanel displayPanel = null;
    private Label lastUpdatedLabel = new Label();
    private Label olLabel = new Label ();
    private Label olsizeLabel = new Label ();
    private Label oltextLabel = new Label ();
    private Label tsLabel = new Label ();
    private Label tssizeLabel = new Label ();
    private Label tstextLabel = new Label ();
    private Label ptLabel = new Label ();
    private Label ptsizeLabel = new Label ();
    private Label pttextLabel = new Label ();

    // Collection of the combined information from PointTrack and TraceState
    // topics.
    private HashMap<Integer, ObjectData> m_objects = new HashMap<Integer, ObjectData>();

    // Map of current objects on the battlefield from the ObjectLocation
    // topic.
    private HashMap<Integer, ObjectLocation> m_battlefield = new HashMap<Integer, ObjectLocation>();

    // Map of labels representing current objects on the battlefield from 
    // as derived from the ObjectLocation topic.
    private HashMap<Integer, IconLabel> m_battlefieldLabels = new HashMap<Integer, IconLabel>();

    private ArrayList<ObjectData> m_info = new ArrayList<ObjectData>();

    //private int iterations = 0;

    // If true, object labels show position information: if false, only the
    // object ID is shown.
    private boolean m_fullObjectInfo = true;

    // If true, all objects on the battlefield will be displayed: if false, only
    // those within sensor range.
    private boolean m_displayBattlefield = true;

    String objectLocationUrl = "/dds/webDemo";
    String objectLocationUrlSuffix = "/Battlefield/track_ObjectLocation_topic/track.ObjectLocation/subscribe/webReader/take";
    int objectLocationSeq = 0;

    String trackStateUrl = "/dds/webDemo";
    String trackStateUrlSuffix = "/Battleship/track_TrackState_topic/track.TrackState/subscribe/webReader/take";
    int trackStateSeq = 0;

    String pointTrackUrl = "/dds/webDemo";
    String pointTrackUrlSuffix = "/Battleship/track_PointTrack_topic/track.PointTrack/subscribe/webReader/take";
    int pointTrackSeq = 0;

    protected Object m_syncObject = new Integer (27);

    private boolean DEBUG = false;
    
    private MapWidget map = null;
    private MapWidget newMap = null;
    
    private GroundOverlay groundOverlay; 

    /**
     * This is the entry point method.
     */
    public void onModuleLoad() {
        Document doc = Document.get();
            
        System.out.println("doc.getURL() = " + doc.getURL());
        String docUrl = doc.getURL();
        int fromIndex = docUrl.indexOf("?clientId=");
        if (fromIndex == -1) {
        	fromIndex = docUrl.indexOf("&clientId=");
        }
        int toIndex = fromIndex;
        if(fromIndex != -1) {
            toIndex = docUrl.indexOf("&", fromIndex + 1);
            fromIndex = fromIndex + 10;
            if(toIndex == -1) {
                toIndex = docUrl.length();
            }
            String clientId = new String ("_").concat(docUrl.substring(fromIndex, toIndex));
            if (clientId.length() > 1) {
                System.out.println ("clientId=" + clientId);
                objectLocationUrl = objectLocationUrl.concat(clientId);
                trackStateUrl = trackStateUrl.concat(clientId);
                pointTrackUrl = pointTrackUrl.concat(clientId);
            }
        }
        objectLocationUrl = objectLocationUrl.concat(objectLocationUrlSuffix);
        trackStateUrl = trackStateUrl.concat(trackStateUrlSuffix);
        pointTrackUrl = pointTrackUrl.concat(pointTrackUrlSuffix);
        
        fromIndex = docUrl.indexOf("?refreshDelay=");
        if (fromIndex == -1) {
        	fromIndex = docUrl.indexOf("&refreshDelay=");
        }
        toIndex = fromIndex;
        if(fromIndex != -1) {
            toIndex = docUrl.indexOf("&", fromIndex + 1);
            fromIndex = fromIndex + 14;
            if(toIndex == -1) {
                toIndex = docUrl.length();
            }
            try {
                Integer refresh_interval = Integer.decode(docUrl.substring(fromIndex, toIndex));
                System.out.println ("refresh_interval=" + refresh_interval);
                if (refresh_interval.intValue() > 50) {
                    REFRESH_INTERVAL = refresh_interval.intValue();
                }
            } catch (NumberFormatException ex) {
                // Do nothing, leave the refresh interval at 500 ms.
            }
        }

        fromIndex = docUrl.indexOf("?mapsDisplay=");
        if (fromIndex == -1) {
        	fromIndex = docUrl.indexOf("&mapsDisplay=");
        }
        toIndex = fromIndex;
        if(fromIndex != -1) {
            toIndex = docUrl.indexOf("&", fromIndex + 1);
            fromIndex = fromIndex + 13;
            if(toIndex == -1) {
                toIndex = docUrl.length();
            }
            Boolean maps_display = Boolean.valueOf(docUrl.substring(fromIndex, toIndex));
            System.out.println ("maps_display=" + maps_display);
            MAPS_DISPLAY = maps_display.booleanValue();
        }
       
        if (MAPS_DISPLAY) {
            LatLng scg = LatLng.newInstance(-33.891683, 151.224841);    
            
            // Open a map centered on The Sydney Cricket Ground, Sydney, Australia    
            newMap = new MapWidget(scg, 16);    
            newMap.setSize("400px", "400px");
            newMap.setDraggable(false);

            // Add styles to the map.
            newMap.addStyleName("silverBorder");
            newMap.addStyleName("upperPanel");
        } else {
        	displayPanel = new AbsolutePanel ();
        	
            // Create panel for track display.
            displayPanel.setPixelSize(Constants.BATTLEFIELD_MAX_X, Constants.BATTLEFIELD_MAX_Y);

            // Add styles to the track display panel.
            displayPanel.addStyleName("silverBorder");
            displayPanel.addStyleName("displayPanel");
            Image circle = new Image ();
            circle.setUrl(circleImage);
            displayPanel.add(circle);
        }
        	        
        // Create table for track data.
        infoTable.setText(0, 0, "ID");
        infoTable.setText(0, 1, "Dir");
        infoTable.setText(0, 2, "Dist");
        infoTable.setText(0, 3, "Ang");

        // Add styles to elements in the track data table.
        infoTable.setCellPadding(6);
        infoTable.addStyleName("infoTable");
        infoTable.getRowFormatter().addStyleName(0, "tableHeader");
        infoTable.getColumnFormatter().addStyleName(0, "infoTableColumn");
        infoTable.getColumnFormatter().addStyleName(1, "infoTableColumn");
        infoTable.getColumnFormatter().addStyleName(2, "infoTableColumn");
        infoTable.getColumnFormatter().addStyleName(3, "infoTableColumn");

        // Create the table for the summary data.
        summaryGrid.setText(0, 0, "Total");
        summaryGrid.setText(0, 1, "No Position");
        summaryGrid.setText(0, 2, "Unidentified");
        summaryGrid.setText(0, 3, "Unknown");
        summaryGrid.setText(0, 4, "Friendly");
        summaryGrid.setText(0, 5, "Hostile");
        summaryGrid.setText(1, 0, "0");
        summaryGrid.setText(1, 1, "0");
        summaryGrid.setText(1, 2, "0");
        summaryGrid.setText(1, 3, "0");
        summaryGrid.setText(1, 4, "0");
        summaryGrid.setText(1, 5, "0");

        // Add styles to elements in the track data table.
        summaryGrid.setCellPadding(6);
        summaryGrid.getRowFormatter().addStyleName(0, "tableHeader");
        summaryGrid.addStyleName("summaryGrid");
        summaryGrid.addStyleName("silverBorder");

        // Assemble the infoAndSummaryPanel.
        infoTableScrollPanel.add(infoTable);
        infoTableScrollPanel.addStyleName("infoScrollPanel");
        infoTableScrollPanel.addStyleName("silverBorder");
        infoAndSummaryPanel.add(infoTableScrollPanel);
        infoAndSummaryPanel.add(summaryGrid);

        // Assemble the upperPanel.
        if (MAPS_DISPLAY) {
        	upperPanel.add(newMap);
        } else {
        	upperPanel.add(displayPanel);
        }
        upperPanel.add(infoAndSummaryPanel);
        upperPanel.addStyleName("upperPanel");

        // Assemble the mainPanel.
        mainPanel.add(upperPanel);
        mainPanel.addStyleName("mainPanel");
        mainPanel.add(lastUpdatedLabel);
        mainPanel.add(olLabel);
        mainPanel.add(olsizeLabel);
        mainPanel.add(oltextLabel);        
        mainPanel.add(tsLabel);
        mainPanel.add(tssizeLabel);
        mainPanel.add(tstextLabel);
        mainPanel.add(ptLabel);
        mainPanel.add(ptsizeLabel);
        mainPanel.add(pttextLabel);

        // Associate the Main panel with the HTML host page.
        RootPanel.get("displayApp").add(mainPanel);

        // Setup timer to refresh list automatically.
        Timer refreshTimer = new Timer() {
            @Override
            public void run() {
                refreshDisplay();
            }
        };
        refreshTimer.scheduleRepeating(REFRESH_INTERVAL);
        
        if (MAPS_DISPLAY) {
            // The map's bounds are meaningless until the map has been added to the DOM 
            // and sized appropriately 
            LatLngBounds bounds = newMap.getBounds(); 
            LatLng southWest = bounds.getSouthWest(); 
            LatLng northEast = bounds.getNorthEast(); 
         
            // generate bounds that covers center map with full width and height
            LatLngBounds rectBounds = LatLngBounds.newInstance(southWest, northEast); 
            groundOverlay = new GroundOverlay(circleImage, rectBounds); 
            newMap.addOverlay(groundOverlay);
            displayPanel = newMap.getPane(MapPaneType.MAP_PANE);
        }
    }

    public class TrackStateCallback implements RequestCallback {
        public void onError(Request request, Throwable exception) {
            System.out.println ("ERROR");
        } 

        public void onResponseReceived(Request request, Response response) {
            if (DEBUG) {
                System.out.println(request.isPending());
                System.out.println(response.getText());
                tsLabel.setText("TrackState response : code " + response.getStatusCode() + " " + response.getStatusText() + ", headers : " + response.getHeadersAsString());
                tssizeLabel.setText("TrackState response length : " + Integer.toString(response.getText().length()));
                tstextLabel.setText("TrackState response text : " + response.getText());
            }

            if(response.getText().length() > 0) {
                String resultString = response.getText();
                int fromIndex = resultString.indexOf("\"data\"");
                int toIndex = 0;
                while(fromIndex != -1) {
                    fromIndex = resultString.indexOf("\"trackId\":", fromIndex);
                    toIndex = resultString.indexOf(",\"classification\":", fromIndex);
                    fromIndex = fromIndex + 10;
                    Integer mapKey = Integer.valueOf(resultString.substring(fromIndex, toIndex));
                    //System.out.println (mapKey);
                    fromIndex = toIndex + 18;
                    fromIndex = resultString.indexOf("{\"__value\":", toIndex + 18);
                    toIndex = resultString.indexOf("},\"padding\":", fromIndex);
                    fromIndex = fromIndex + 11;
                    Integer classValue = Integer.valueOf(resultString.substring(fromIndex, toIndex));
                    //System.out.println (classValue);
                    fromIndex = resultString.indexOf(",\"instance_state\":", toIndex);
                    toIndex = resultString.indexOf(",\"valid_data\":", fromIndex);
                    fromIndex = fromIndex + 18;
                    Integer instanceState = Integer.valueOf(resultString.substring(fromIndex, toIndex));
                    fromIndex = resultString.indexOf("\"data\"", fromIndex);

                    synchronized (m_syncObject) {
                        if (instanceState.intValue() == 1 /*DDS.ALIVE_INSTANCE_STATE.value*/) {
                            updateMap(mapKey.intValue(), Classification_enum.from_int(classValue.intValue()));
                        }
                    }
                }
            }                
        }
    }

    public void getTrackStates () {
        // Add a sequence number to the http request URI to prevent GWT caching getting
        // in the way of returning current results.
        String seqString = new String ("?seqNr=" + trackStateSeq);
        RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, trackStateUrl + seqString);
        trackStateSeq++;
        try {
            Request response = builder.sendRequest(null, new TrackStateCallback());
        } catch (RequestException e) {
            // Code omitted for clarity
        }

        /*updateTable (IdentificationData.getTrackStates());*/
    }

    public class PointTrackCallback implements RequestCallback {
        public void onError(Request request, Throwable exception) {
            System.out.println ("ERROR");
        } 

        public void onResponseReceived(Request request, Response response) {
            if (DEBUG) {
                System.out.println(request.isPending());
                System.out.println(response.getText());
                ptLabel.setText("PointTrack response : code " + response.getStatusCode() + " " + response.getStatusText() + ", headers : " + response.getHeadersAsString());
                ptsizeLabel.setText("PointTrack response length : " + Integer.toString(response.getText().length()));
                pttextLabel.setText("PointTrack response text : " + response.getText());
            }

            if(response.getText().length() > 0) {
                String resultString = response.getText();
                int fromIndex = resultString.indexOf("\"data\"");
                int toIndex = 0;
                while(fromIndex != -1) {
                    fromIndex = resultString.indexOf("\"trackId\":", fromIndex);
                    toIndex = resultString.indexOf(",\"position\":", fromIndex);
                    fromIndex = fromIndex + 10;
                    Integer mapKey = Integer.valueOf(resultString.substring(fromIndex, toIndex));
                    //System.out.println (mapKey);
                    fromIndex = resultString.indexOf("{\"distance\":", toIndex + 12);
                    fromIndex = fromIndex + 12;
                    toIndex = resultString.indexOf(",\"direction\":", fromIndex);
                    Float distValue = Float.valueOf(resultString.substring(fromIndex, toIndex));
                    //System.out.println (distValue);
                    fromIndex = toIndex + 13;
                    toIndex = resultString.indexOf(",\"angle\":", fromIndex);
                    Float dirValue = Float.valueOf(resultString.substring(fromIndex, toIndex));
                    //System.out.println (dirValue);
                    fromIndex = toIndex + 9;
                    toIndex = resultString.indexOf("},\"padding\":", fromIndex);
                    Float angValue = Float.valueOf(resultString.substring(fromIndex, toIndex));
                    //System.out.println (angValue);
                    fromIndex = toIndex + 12;
                    toIndex = resultString.indexOf("}, \"info\":", fromIndex);
                    //String padding = resultString.substring(fromIndex, toIndex);
                    //System.out.println(padding);
                    fromIndex = resultString.indexOf(",\"instance_state\":", toIndex);
                    toIndex = resultString.indexOf(",\"valid_data\":", fromIndex);
                    fromIndex = fromIndex + 18;
                    Integer instanceState = Integer.valueOf(resultString.substring(fromIndex, toIndex));
                    //System.out.println (instanceState);
                    fromIndex = resultString.indexOf("\"data\"", fromIndex);

                    synchronized (m_syncObject) {
                        if (instanceState.intValue() != 1 /*DDS.ALIVE_INSTANCE_STATE.value*/) {
                            revertBattlefieldLabel(mapKey);
                            m_objects.remove(mapKey);
                        } else {
                            Pos pos = new Pos (distValue.floatValue(), 
                                    dirValue.floatValue(), angValue.floatValue());
                            updateMap(mapKey.intValue(), pos);
                        }
                    }
                }
            }
        }
    }

    public void getPointTracks () {
        // Add a sequence number to the http request URI to prevent GWT caching getting
        // in the way of returning current results.
        String seqString = new String ("?seqNr=" + pointTrackSeq);
        RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, pointTrackUrl + seqString);
        pointTrackSeq++;
        try {
            Request response = builder.sendRequest(null, new PointTrackCallback());
        } catch (RequestException e) {
            // Code omitted for clarity
        }

        /*updateTable (SensorData.getPointTracks());
        Integer[] disposedIds = SensorData.getDisposedPointTrackIds();
        for(int i = 0; i < disposedIds.length; i++) {
            //System.out.println ("Disposed point track object " + disposedIds[i]);
            m_objects.remove(disposedIds[i]);
            revertBattlefieldLabel(disposedIds[i]);
        }*/           
    }

    public class ObjectLocationCallback implements RequestCallback {
        public void onError(Request request, Throwable exception) {
            System.out.println ("ERROR");
        } 

        public void onResponseReceived(Request request, Response response) {
            if (DEBUG) {
                System.out.println(request.isPending());
                System.out.println(response.getText());
                olLabel.setText(response.getText());
                olLabel.setText("ObjectLocation response : code " + response.getStatusCode() + " " + response.getStatusText() + ", headers : " + response.getHeadersAsString());
                olsizeLabel.setText("ObjectLocation response length : " + Integer.toString(response.getText().length()));
                oltextLabel.setText("ObjectLocation response text : " + response.getText());
            }

            if(response.getText().length() > 0) {
                String resultString = response.getText();
                int fromIndex = resultString.indexOf("\"data\"");
                int toIndex = 0;
                while(fromIndex != -1) {
                    fromIndex = resultString.indexOf("\"trackId\":", fromIndex);
                    toIndex = resultString.indexOf(",\"x\":", fromIndex);
                    fromIndex = fromIndex + 10;
                    Integer mapKey = Integer.valueOf(resultString.substring(fromIndex, toIndex));
                    //System.out.println (mapKey);
                    fromIndex = toIndex + 5;
                    toIndex = resultString.indexOf(",\"y\":", fromIndex);
                    Integer xValue = Integer.valueOf(resultString.substring(fromIndex, toIndex));
                    //System.out.println (xValue);
                    fromIndex = toIndex + 5;
                    toIndex = resultString.indexOf(",\"z\":", fromIndex);
                    Integer yValue = Integer.valueOf(resultString.substring(fromIndex, toIndex));
                    //System.out.println (yValue);
                    fromIndex = toIndex + 5;
                    toIndex = resultString.indexOf(",\"padding\":", fromIndex);
                    Integer zValue = Integer.valueOf(resultString.substring(fromIndex, toIndex));
                    //System.out.println (zValue);
                    fromIndex = toIndex + 11;
                    toIndex = resultString.indexOf("}, \"info\":", fromIndex);
                    //String padding = resultString.substring(fromIndex, toIndex);
                    //System.out.println(padding);
                    fromIndex = resultString.indexOf(",\"instance_state\":", toIndex);
                    toIndex = resultString.indexOf(",\"valid_data\":", fromIndex);
                    fromIndex = fromIndex + 18;
                    Integer instanceState = Integer.valueOf(resultString.substring(fromIndex, toIndex));
                    //System.out.println (instanceState);
                    fromIndex = resultString.indexOf("\"data\"", fromIndex);

                    synchronized (m_battlefield) {
                        if (instanceState.intValue() != 1 /*DDS.ALIVE_INSTANCE_STATE.value*/) {
                            removeLabelFromBattlefield (mapKey);
                            m_battlefield.remove(mapKey);
                            //System.out.println ("Disposed battle field object " + disposedIds[i]);
                        } else {
                            //System.out.println("Hello");
                            ObjectLocation mapEntry = (ObjectLocation) m_battlefield
                            .get(mapKey);
                            if (mapEntry == null) {
                                mapEntry = new ObjectLocation();
                                mapEntry.trackId = mapKey.intValue();
                                m_battlefield.put(mapKey, mapEntry);
                            }

                            mapEntry.x = xValue.intValue();
                            mapEntry.y = yValue.intValue();
                            mapEntry.z = zValue.intValue();
                        }
                    }
                }
            }               
        }
    }

    public void getObjectLocations () {
        // Add a sequence number to the http request URI to prevent GWT caching getting
        // in the way of returning current results.
        String seqString = new String ("?seqNr=" + objectLocationSeq);
        RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, objectLocationUrl + seqString);
        objectLocationSeq++;
        try {
            Request response = builder.sendRequest(null, new ObjectLocationCallback());
        } catch (RequestException e) {
            // Code omitted for clarity
        }

        // Test data code for client side only demo.
        //update (BattlefieldData.getObjectLocations());
        //Integer[] disposedIds = BattlefieldData.getDisposedObjectLocationIds();
        //for(int i = 0; i < disposedIds.length; i++) {
        //System.out.println ("Disposed battle field object " + disposedIds[i]);
        //removeLabelFromBattlefield (disposedIds[i]);
        //m_battlefield.remove(disposedIds[i]);
        //m_objects.remove(disposedIds[i]);
        //m_info.remove(disposedIds[i]);
        //}
    }


    public void updateTable (TrackState[] states) {
        for (int i = 0; i < states.length; i++) {
            TrackState state = states[i];
            updateMap(state.trackId, state.classification);
        }
    }


    public void updateTable (PointTrack[] tracks) {
        for (int i = 0; i < tracks.length; i++) {
            PointTrack track = tracks[i];
            updateMap(track.trackId, track.position);
        }
    }

    public void update (ObjectLocation[] locations) {
        //System.out.println ("length = " + locations.length);
        for (int i = 0; i < locations.length; i++) {
            ObjectLocation location = locations[i];
            updateMap (location);
        }
    }

    private void updateMap(int id, Classification_enum classification) {
        synchronized (m_objects) {
            Integer key = new Integer(id);
            ObjectData data = m_objects.get(key);
            if (data == null) {
                data = new ObjectData();
                //System.out.println ("updateMap Classification new object : " + id);
                data.m_id = id;
                data.m_position = NO_POSITION;
                m_objects.put(key, data);
            }

            data.m_classification = classification;
        }
    }    

    private void updateMap(int id, Pos position) {
        synchronized (m_objects) {
            Integer key = new Integer(id);
            ObjectData data = m_objects.get(key);
            if (data == null) {
                data = new ObjectData();
                //System.out.println ("updateMap Position new object : " + id);
                data.m_id = id;
                data.m_classification = Classification_enum.TRACK_CLASSIFICATION_UNIDENTIFIED;
                m_objects.put(key, data);
            }

            data.m_position = position;
        }  
    }

    public void updateMap (ObjectLocation location) {
        Integer mapKey = new Integer(location.trackId);

        ObjectLocation mapEntry = (ObjectLocation) m_battlefield
        .get(mapKey);
        if (mapEntry == null) {
            mapEntry = new ObjectLocation();
            //System.out.println ("updateMap Position new object : " + location.trackId);
            mapEntry.trackId = location.trackId;
            m_battlefield.put(mapKey, mapEntry);
        }            

        mapEntry.x = location.x;
        mapEntry.y = location.y;
        mapEntry.z = location.z;
    }

    public void refreshDisplay () {
        getObjectLocations();
        getPointTracks();
        getTrackStates();

        synchronized (m_battlefield) {
            //System.out.println ("m_battlefield.size() =" + m_battlefield.size ());
            Set<Integer> keys = m_battlefield.keySet();
            Iterator<Integer> iter = keys.iterator();
            while (iter.hasNext()) {
                Integer mapKey = iter.next();
                ObjectLocation location = (ObjectLocation) m_battlefield.get(mapKey);
                boolean inRange = false;
                String label = null;
                //System.out.println ("m_objects.size () = " + m_objects.size());
                synchronized (m_objects) {
                    ObjectData data = m_objects.get(mapKey);
                    inRange = (data == null ? false : true);

                    if (!inRange && !m_displayBattlefield) {
                        continue;
                    }

                    if (!m_fullObjectInfo) {
                        label = Integer.toString(mapKey.intValue());
                    } else {
                        if (inRange) {
                            label = data.toStringShort();
                        } else {
                            StringBuffer sb = new StringBuffer();
                            sb.append(location.trackId);
                            sb.append(" ");
                            sb.append(location.x);
                            sb.append(",");
                            sb.append(location.y);
                            sb.append(",");
                            sb.append(location.z);
                            label = sb.toString();
                        }
                    }
                }

                displayObject(mapKey, label, location.x, location.y);
            }
        }

        // Summary counts.
        int countNoPosition = 0;
        int countUnidentified = 0;
        int countUnknown = 0;
        int countFriendly = 0;
        int countHostile = 0;

        // Process all objects reported by the sensor.
        synchronized (m_objects) {
            m_info.clear();
            Iterator<ObjectData> iter = m_objects.values().iterator();
            IconLabel trackLabel = null;
            while (iter.hasNext()) {
                ObjectData data = iter.next();
                trackLabel = m_battlefieldLabels.get(data.m_id);
                // Skip data that does not have position information.
                if (data.m_position == NO_POSITION) {
                    countNoPosition++;
                    continue;
                }

                if (trackLabel != null) {
                    switch (data.m_classification.value()) {
                    case Classification_enum._TRACK_CLASSIFICATION_UNIDENTIFIED:
                        trackLabel.setIconUrl(trackImage);
                        trackLabel.setStyleName("track");
                        countUnidentified++;
                        break;
                    case Classification_enum._TRACK_CLASSIFICATION_UNKNOWN:
                        trackLabel.setIconUrl(unknownTrackImage);
                        trackLabel.setStyleName("unknownTrack");
                        countUnknown++;
                        break;
                    case Classification_enum._TRACK_CLASSIFICATION_FRIENDLY:
                        trackLabel.setIconUrl(friendlyTrackImage);
                        trackLabel.setStyleName("friendlyTrack");
                        countFriendly++;
                        break;
                    case Classification_enum._TRACK_CLASSIFICATION_HOSTILE:
                        trackLabel.setIconUrl(hostileTrackImage);
                        trackLabel.setStyleName("hostileTrack");
                        countHostile++;
                        break;
                    default:
                        System.out.println("Unexpected object category "
                                + data.m_classification.value()
                                + " for object ID " + data.m_id);
                    //col = Color.MAGENTA;
                    }
                }

                m_info.add(data);
            }
            updateInfoTable();
            updateSummaryTable(countNoPosition, 
                    countUnidentified, 
                    countUnknown, 
                    countFriendly, 
                    countHostile);

        }
        // Display timestamp showing last refresh.
        lastUpdatedLabel.setText("Last update : "
                + DateTimeFormat.getMediumDateTimeFormat().format(new Date()));
    }

    /**
     * Displays an individual object, using whatever scale and
     * transformation are in place on the Graphics2D instance.
     * 
     * @param label
     *            String to place next to the object's marker
     * @param x
     *            X coordinate
     * @param y
     *            Y coordinate
     */
    private void displayObject(Integer id, String label, int x, int y) {
        IconLabel widget = m_battlefieldLabels.get(id);
        if (widget == null) {
            widget = new IconLabel(trackImage, label);
            m_battlefieldLabels.put(id, widget);
        }            
        int index = displayPanel.getWidgetIndex(widget);
        x = x - widget.getHorizontalImageOffset();
        y = y - widget.getVerticalImageOffset();
        if (index == -1) {
            displayPanel.add(widget, x, y);
        } else {
            widget.setLabelText(label);
            displayPanel.setWidgetPosition(widget, x, y);
        }
    }

    private void removeLabelFromBattlefield(Integer id) {
        IconLabel widget = m_battlefieldLabels.remove(id);
        if (widget != null) {
            displayPanel.remove(widget);
        }
    }

    private void revertBattlefieldLabel(Integer id) {
        IconLabel widget = m_battlefieldLabels.get(id);
        if (widget != null) {
            widget.setIconUrl(trackImage);
            widget.setStyleName("track");
        }
    }

    public void updateInfoTable () {
        for (int i = 0; i < m_info.size (); i++) {
            updateInfoTable (m_info.get(i), i + 1);

            // Clear any errors.
            //errorMsgLabel.setVisible(false);
        }

        // infoTable.getRowCount() - 1 because the "header" counts as 
        // part of the real table data, but shouldn't be counted.
        while ((infoTable.getRowCount() - 1) > m_info.size()) {
            infoTable.removeRow(infoTable.getRowCount() - 1);
        }
    }

    private void updateSummaryTable(int countNoPosition, int countUnidentified,
            int countUnknown, int countFriendly, int countHostile) {
        String countText = Integer.toString(m_objects.size());
        String noPosText = Integer.toString((int) countNoPosition);
        String unIdText = Integer.toString((int) countUnidentified);
        String unKnText = Integer.toString((int) countUnknown);
        String friendlyText = Integer.toString((int) countFriendly);
        String hostileText = Integer.toString((int) countHostile);

        summaryGrid.setText(1, 0, countText);
        summaryGrid.setText(1, 1, noPosText);
        summaryGrid.setText(1, 2, unIdText);
        summaryGrid.setText(1, 3, unKnText);        
        summaryGrid.setText(1, 4, friendlyText);        
        summaryGrid.setText(1, 5, hostileText);        
    }

    private void updateInfoTable(ObjectData data, int row) {
        String idText = Integer.toString(data.m_id);
        String dirText = Integer.toString((int) data.m_position.direction);
        String disText = Integer.toString((int) data.m_position.distance);
        String angText = Integer.toString((int) data.m_position.angle);

        infoTable.setText(row, 0, idText);
        infoTable.setText(row, 1, dirText);
        infoTable.setText(row, 2, disText);
        infoTable.setText(row, 3, angText);
    }

    /**
     * Combined information for an object.
     */
    class ObjectData {
        int m_id;

        Pos m_position;

        Classification_enum m_classification;

        public String toString() {
            StringBuffer sb = new StringBuffer("ID: ");
            sb.append(m_id);
            sb.append(", DIR: ");
            sb.append(Math.round(m_position.direction));
            sb.append(", DIST: ");
            sb.append(Math.round(m_position.distance));
            sb.append(", ANG: ");
            sb.append(Math.round(m_position.angle));

            return sb.toString();
        }

        public String toStringShort() {
            StringBuffer sb = new StringBuffer();
            sb.append(m_id);
            sb.append(" (");
            sb.append(Math.round(m_position.direction));
            sb.append("/");
            sb.append(Math.round(m_position.distance));
            sb.append("/");
            sb.append(Math.round(m_position.angle));
            sb.append(")");

            return sb.toString();
        }
    } 
}
